package com.example.mytestdemo.linearlayoutmanager.dragon

import android.content.Context
import android.graphics.Path
import android.graphics.PathMeasure
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.annotation.FloatRange
import androidx.recyclerview.widget.RecyclerView.Recycler
import kotlin.math.abs


/**
 * @author tgw
 * @date 2022/8/29
 * @describe
 *
 */
class PathLinearLayoutManager : RecyclerView.LayoutManager {

    companion object {
        private const val TAG = "PathLinearLayoutManager"
    }

    private var mItemOffset = 0

    private lateinit var pathAllPointS: Keyframes

    //滑动方向
    private var mOrientation = RecyclerView.VERTICAL

    //因为我们只需要将Path范围内的Item布局出来，超出范围的就不应该参与计算了，
    // 还记不记得刚刚在初始化Keyframes的时候还算了一个mItemCountInScreen
    // (Path最多能同时出现几个Item) ？ 是时候派上用场了，
    // 我们还要拿到当前Path里面第一个能显示的Item position，
    // 这样的话，能提高不少效率（知道开始position和结束position）。
    private var mItemCountInScreen = 0

    private var mOffsetY = 0f
    private var mOffsetX = 0f
    private var mFirstVisibleItemPos = 0

    /**
     * @param path       目标路径
     * @param itemOffset Item间距
     */
    constructor(context: Context, path: Path, itemOffset: Int)
            : this(context, path, itemOffset, RecyclerView.VERTICAL) {
    }

    /**
     * @param path        目标路径
     * @param itemOffset  Item间距
     * @param orientation 滑动方向
     */
    constructor(
        context: Context,
        path: Path,
        itemOffset: Int,
        @RecyclerView.Orientation orientation: Int
    ) {
        this.mOrientation = orientation
        mItemOffset = itemOffset

        updatePath(path)
    }


    /**
     * 更新Path
     */
    private fun updatePath(path: Path) {
        if (path != null) {
            pathAllPointS = Keyframes(path)

            check(mItemOffset != 0) {
                //这里我们不允许间距为0，因为如果间距为0的话，全部Item都会叠在一起，这样就没有意义了。
                "itemOffset must be > 0 !!!"
            }
            //计算出这个Path最多能同时出现几个Item
            mItemCountInScreen = (pathAllPointS.pathLength / mItemOffset + 1).toInt()
            Log.d(TAG, "updatePath 最多能同时出现几个Item : $mItemCountInScreen")
        }
        requestLayout()
    }


    /**
     * 初始化需要布局的Item数据
     *
     * @param result    结果
     * @param itemCount Item总数
     */
    private fun initNeedLayoutItems(result: MutableList<PosTan>, itemCount: Int) {

        var currentDistance: Float
        //必须从第一个item开始，因为要拿到最小的，也就是最先的
        for (i in 0 until itemCount) {
            currentDistance = i * mItemOffset - getScrollOffset()
            //判断当前距离 >= 0的即表示可见
            if (currentDistance >= 0) {
                //得到第一个可见的position
                mFirstVisibleItemPos = i
                break
            }
        }

        //结束的position
        var endIndex: Int = mFirstVisibleItemPos + mItemCountInScreen
        //防止溢出
        if (endIndex > getItemCount()) {
            endIndex = getItemCount()
        }
        var fraction: Float
        var posTan: PosTan?
        for (i in mFirstVisibleItemPos until endIndex) {
            //得到当前距离,
            // Item在Path上的百分比 = (Item当前position * 指定的Item间距 - 滑动偏移量) / Path总长度
            currentDistance = i * mItemOffset - getScrollOffset()
            //得到百分比
            fraction = currentDistance / pathAllPointS.pathLength
            //根据百分比从Keyframes中取出对应的坐标和角度
            posTan = pathAllPointS.getValue(fraction)
            if (posTan == null) {
                continue
            }
            posTan.index = i
            posTan.fraction = fraction
            //添加进list中
            result.add(posTan)
        }
    }


    override fun onLayoutChildren(recycler: Recycler?, state: RecyclerView.State) {
        if (state.itemCount == 0) {
            //没有Item可布局，就回收全部临时缓存 (参考自带的LinearLayoutManager)
            //这里的没有Item，是指Adapter里面的数据集，
            //可能临时被清空了，但不确定何时还会继续添加回来
            removeAndRecycleAllViews(recycler!!)
            return
        }
        //暂时分离和回收全部有效的Item
        detachAndScrapAttachedViews(recycler!!)

        relayoutChildren(recycler, state)
    }

    /**
     * 判断是否满足无限循环滚动条件
     * 条件： 必须明确开启无限循环模式
     */
    fun isLoopScrollMode(): Boolean {
        return true
    }

    /**
     * 判断是否满足无限循环滚动条件
     * 条件： 必须明确开启无限循环模式，并且Item的总长度要大于Path的总长度
     */
    private fun isSatisfiedLoopScroll(): Boolean {
        val pathLength: Float = pathAllPointS.pathLength
        val itemLength = getItemLength()
        return isLoopScrollMode() && itemLength - pathLength > mItemOffset
    }


    /**
     * 获取空缺Item的个数
     */
    private fun getVacantCount(): Int {
        //item总长度
        val itemLength = getItemLength()

        //path的长度
        val pathLength: Float = pathAllPointS.pathLength

        //第一个item较Path终点的偏移量，这个偏移量是以Path的终点为起点的，
        //例如 现在一共有10个item：
        // 0___1___2___3___4___5 现在的偏移量是>0的，直到：
        // 5___6___7___8___9___0 时为0，这个时候继续向右边滚动的话，就会变成负数了
        val firstItemScrollOffset = (getScrollOffset() + pathLength).toInt()

        //同上，区别就是上面的是第一个item，这个是最后一个item，
        //例如 现在一共有10个item：
        // 0___1___2___3___4___5 现在的偏移量是<0的，一直到：
        // 4___5___6___7___8___9 时为0
        //这样做就是为了：当最后一个item离开它应在的位置时(常规的滑动模式最后一个item是坐死在最后的位置的)，
        //能够及时知道，并开始计算出它下一个item索引来补上它的空位
        val lastItemScrollOffset = firstItemScrollOffset - itemLength
        //item的总长度 + path的总长度
        val lengthOffset = itemLength + pathLength

        //当最后一个item滑出屏幕时(根据上面的例子来讲，是向左边滑)：
        // 9_|_0___1___2___3___4
        // 开始计算的偏移量（正数），因为如果超出了屏幕而不作处理的话，
        // 下面计算空缺距离的时候，最大值只能是itemLength
        val lastItemOverflowOffset =
            if (firstItemScrollOffset > lengthOffset) firstItemScrollOffset - lengthOffset else 0

        //空缺的距离
        val vacantDistance =
            (lastItemScrollOffset % itemLength).toInt() + lastItemOverflowOffset.toInt()
        Log.d(TAG, "getVacantCount 补充: ${vacantDistance / mItemOffset}")
        //空缺的距离 / item之间的距离 = 需补上的item个数
        return vacantDistance / mItemOffset
    }

    /**
     * 初始化需要布局的Item数据 （无限滚动模式）
     *
     * @param result    结果
     * @param itemCount Item总数
     */
    private fun initNeedLayoutLoopScrollItems(result: MutableList<PosTan>, itemCount: Int) {
        val vacantCount = getVacantCount()
        //得出第一个可见的item索引
        mFirstVisibleItemPos = vacantCount - mItemCountInScreen - 1
        var currentDistance: Float
        var fraction: Float
        var posTan: PosTan?
        var pos: Int
        for (i in mFirstVisibleItemPos until vacantCount) {
            //防止溢出
            pos = i % itemCount
            if (pos < 0) {
                //比如现在一个有10个item，当前pos=-10，那就表示它对应的索引是0了
                if (pos == -itemCount) {
                    pos = 0
                } else {
                    //将负数转成有效的索引
                    // [0,1,2,3,4,5,6,7,8,9]
                    // -9 --> 1   -8 --> 2
                    pos += itemCount
                }
            }
            //得出当前距离
            currentDistance = (i + itemCount) * mItemOffset - getScrollOffset()
            fraction = currentDistance / pathAllPointS.pathLength
            //拿到坐标数据
            posTan = pathAllPointS.getValue(fraction)
            if (posTan == null) {
                continue
            }
            posTan.index = pos
            posTan.fraction = fraction
            //添加进list中
            result.add(posTan)
        }
    }


    fun getLoopScrollCount(result: MutableList<PosTan>, itemCount: Int) {
        //item总长度
        val itemLength = getItemLength()
        //path的长度
        val pathLength: Float = pathAllPointS.pathLength

        val scrollLength = getScrollOffset() + pathLength

        if (scrollLength < itemLength) {
            initNeedLayoutItems(result, itemCount)
            return
        }


        val needBlankLength = scrollLength - itemLength

        val needBlankItem = (needBlankLength / mItemOffset % itemCount).toInt()

        Log.d(TAG, "getLoopScrollCount 补充: ${needBlankItem}")

        var startIndex: Int = needBlankItem - mItemCountInScreen


        if (startIndex < 0) {
//            startIndex = abs(startIndex)
            startIndex = itemCount - abs(startIndex)
        }
        //结束的position
        var endIndex: Int = itemCount + needBlankItem - 1

        var currentDistance = 0f
        var fraction: Float
        var posTan: PosTan?
        Log.d(
            TAG,
            "getLoopScrollCount 补充 startIndex: ${startIndex}---endIndex:$endIndex----needBlankItem:$needBlankItem"
        )

        for (i in startIndex until endIndex) {
            //得到当前距离,
            // Item在Path上的百分比 = (Item当前position * 指定的Item间距 - 滑动偏移量) / Path总长度
            currentDistance = i * mItemOffset - getScrollOffset()
            Log.d(TAG, "getLoopScrollCount 补充 currentDistance: $currentDistance")

            //得到百分比
            fraction = currentDistance / pathAllPointS.pathLength
            //根据百分比从Keyframes中取出对应的坐标和角度
            posTan = pathAllPointS.getValue(fraction)
            if (posTan == null) {
                continue
            }
            posTan.index = i % itemCount
            posTan.fraction = fraction
            //添加进list中
            result.add(posTan)
        }
    }

    /**
     * 回收屏幕外需回收的Item
     * 这个方法不知道为什么越界
     */
    private fun recycleChildren(recycler: Recycler) {
//        val scrapList = recycler.scrapList
//        for (i in 0 until scrapList.size) {
//            if ( scrapList[i] != null) {
//                val holder = scrapList[i]
//                removeView(holder.itemView)
//                recycler.recycleView(holder.itemView)
//            }
//        }
    }

    private fun recycleChildren(recycler: Recycler, needLayoutDataList: List<PosTan>) {
        //item总数
        val itemCount = itemCount
        //列表中第一个能看到的item索引
        //不用担心IndexOutOfBoundsException的问题，
        //因为我们在上面已经做了判断，如果list为空，直接return了，不会执行到这里
        val firstIndex = needLayoutDataList[0].index
        //最后一个能看到的item索引
        val lastIndex = needLayoutDataList[needLayoutDataList.size - 1].index
        //前面那一段的起始和结束索引
        var forwardStartIndex: Int
        val forwardEndIndex: Int
        //后面一段的起始和结束索引
        val backwardStartIndex: Int
        var backwardEndIndex: Int
        //等下还需判断是否需要回收
        var needRecyclerForward = false
        var needRecyclerBackward = false

        //排除第一个，所以-1
        forwardEndIndex = firstIndex - 1
        //回收的范围 = 列表中能同时显示的数量
        forwardStartIndex = forwardEndIndex - mItemCountInScreen
        //排除最后一个，所以+1
        backwardStartIndex = lastIndex + 1
        //回收的范围 = 列表中能同时显示的数量
        backwardEndIndex = backwardStartIndex + mItemCountInScreen

        //如果第一个显示的item索引为0，就不用回收了
        if (firstIndex > 0) {
            if (forwardStartIndex < 0) {
                forwardStartIndex = 0
            }
            //标记需要回收
            needRecyclerForward = true
        }
        //如果adapter数据集中最后一个item正在显示，也不用回收
        if (lastIndex < itemCount - 1) {
            if (backwardEndIndex >= itemCount) {
                backwardEndIndex = itemCount - 1
            }
            //标记需要回收
            needRecyclerBackward = true
        }
        //回收前半段
        if (needRecyclerForward) {
            recycleChildren(recycler, forwardStartIndex, forwardEndIndex)
        }
        //回收后半段
        if (needRecyclerBackward) {
            recycleChildren(recycler, backwardStartIndex, backwardEndIndex)
        }
    }

    private fun recycleChildren(recycler: Recycler, startIndex: Int, endIndex: Int) {
        if (startIndex == endIndex) {
            return
        }
        for (i in startIndex..endIndex) {
            val view = recycler.getViewForPosition(i)
            if (view != null) {
                removeView(view)
                recycler.recycleView(view)
            }
        }
    }


    override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return RecyclerView.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
    }

    override fun onMeasure(
        recycler: Recycler,
        state: RecyclerView.State,
        widthSpec: Int,
        heightSpec: Int
    ) {
        var widthSpec = widthSpec
        var heightSpec = heightSpec
        if (pathAllPointS != null) {
            val widthMode = View.MeasureSpec.getMode(widthSpec)
            val heightMode = View.MeasureSpec.getMode(heightSpec)
            //如果RecyclerView宽度设置了wrap_content
            //那就把宽度设置为Path的宽度
            if (widthMode == View.MeasureSpec.AT_MOST) {
                widthSpec =
                    View.MeasureSpec.makeMeasureSpec(
                        pathAllPointS.mMaxX.toInt() + 200,
                        View.MeasureSpec.EXACTLY
                    )
                Log.d(TAG, "onMeasure widthMode : AT_MOST")
                Log.d(
                    TAG,
                    "onMeasure widthSpec : $widthSpec---mMaxX:${pathAllPointS.mMaxX.toInt()}---widthSpecSize:${
                        View.MeasureSpec.getSize(widthSpec)
                    }"
                )
            }
            //如果RecyclerView高度设置了wrap_content
            //那就把高度设置为Path的高度
            if (heightMode == View.MeasureSpec.AT_MOST) {
                heightSpec =
                    View.MeasureSpec.makeMeasureSpec(
                        pathAllPointS.mMaxY.toInt() + 200,
                        View.MeasureSpec.EXACTLY
                    )
            }
        }
        super.onMeasure(recycler, state, widthSpec, heightSpec)
    }

    /**
     * 确定Item位置，角度以及尺寸
     *
     * @param needLayoutItems 需要布局的Item
     */
    private fun onLayout(recycler: Recycler, needLayoutItems: List<PosTan>) {
        var x: Int
        var y: Int
        var item: View
        for (tmp in needLayoutItems) {
            //根据position获取View
            item = recycler.getViewForPosition(tmp.index)
            //添加进去，当然里面不一定每次都是调用RecyclerView的addView方法的，
            //如果是从缓存区里面找到的，只需调用attachView方法把它重新连接上就行了。
            addView(item)
            //测量item，当然，也不是每次都会调用measure方法进行测量的，
            //它里面会判断，如果已经测量过，而且当前尺寸又没有收到更新的通知，就不会重新测量。
            measureChild(item, 0, 0)
            //Path线条在View的中间
            x = tmp.x.toInt() - getDecoratedMeasuredWidth(item) / 2
            y = tmp.y.toInt() - getDecoratedMeasuredHeight(item) / 2

            //进行布局
            layoutDecorated(
                item,
                x,
                y,
                x + getDecoratedMeasuredWidth(item),
                y + getDecoratedMeasuredHeight(item)
            )
            //旋转item
            Log.d(TAG, "onLayout:${tmp.angle} ")
            item.rotation = tmp.angle
        }
    }

    override fun scrollVerticallyBy(dy: Int, recycler: Recycler, state: RecyclerView.State): Int {

        //分离和临时回收
        detachAndScrapAttachedViews(recycler)
        //临时记录上一次的offset
        var lastOffset = mOffsetY
        //更新偏移量
        updateOffsetY(dy.toFloat(), recycler, state)
        //布局item
        relayoutChildren(recycler, state)
        //如果offset没有改变，那么就直接return 0，表示不消费本次滑动
        return if (lastOffset == mOffsetY) 0 else dy

    }

    /**
     *1.获取布局的位置点
     * 2.回收屏幕外item
     */
    private fun relayoutChildren(recycler: Recycler, state: RecyclerView.State) {
        val needLayoutItems: MutableList<PosTan> = ArrayList()
        //获取需要布局的items
//        getVacantCount()
//        getLoopScrollCount(needLayoutItems,itemCount)

        //循环模式
        if (isSatisfiedLoopScroll()) {
            initNeedLayoutLoopScrollItems(needLayoutItems, itemCount)
        } else {
            initNeedLayoutItems(needLayoutItems, itemCount)
        }

        //判断一下状态
        if (needLayoutItems.isEmpty() || state.itemCount == 0 || pathAllPointS == null) {
            removeAndRecycleAllViews(recycler)
            return
        }
        //开始布局
        onLayout(recycler, needLayoutItems)
        //回收
        recycleChildren(recycler)
        recycleChildren(recycler, needLayoutItems)
    }

    /**
     *是否溢出滑动
     */
    fun isOverflowMode(): Boolean {
        return false
    }

    /**
     * 更新Y轴偏移量
     *
     * @param offsetY 偏移量
     */
    private fun updateOffsetY(offsetY: Float, recycler: Recycler?, state: RecyclerView.State) {
        //更新offset
        mOffsetY += offsetY
        //路径总长度
        val pathLength: Float = pathAllPointS.pathLength
        //item总长度
        val itemLength: Int = getItemLength()
        //item总长度相对于路径总长度多出来的部分
        val overflowLength = itemLength - pathLength

        Log.d(
            TAG,
            "updateOffsetY getItemLength：$itemLength--- mOffsetY:$mOffsetY-----pathLength:$pathLength ----overflowLength:$overflowLength"
        )


        //需满足无限循环滚动的条件
        if ((isSatisfiedLoopScroll())) {//无限滚动模式
            //如果全部item即将向上滑出屏幕，这个时候如果是无限循环的话，
            //那么就是会显示第0个item，所以我们可以偷梁换柱，把早已滑出屏幕的第0个item，
            //移回屏幕中，不用担心有什么副作用，这就像一个矩形，旋转了361度和旋转了1度是一样的道理
            if (mOffsetY > itemLength) {
                mOffsetY %= itemLength;
                //因为是向前偏移了一个item的距离
                mOffsetY -= mItemOffset;
            } else if (mOffsetY <= -pathLength) {
                //原理同上
                mOffsetY += itemLength;
                mOffsetY += mItemOffset;
            }
        }else if (isOverflowMode()) {
            //如果是溢出模式,为什么不会消费了，因为上次记录的滚得mOffsetY的值与这次的一样，
            //当item全部滑出屏幕后，为了能及时出现，须把滑动的距离调整下
            //如果是全部向下溢出的，限制最多只能是path的长度
            if (mOffsetY < -pathLength) {
                mOffsetY = -pathLength
            } else if (mOffsetY > itemLength) {
                //如果是向上溢出，那就限制最多只能滑动到item的总长度
                mOffsetY = itemLength.toFloat()
            }
        } else {
            //普通模式
            if (mOffsetY < 0) {
                //避免第一个item脱离顶部向下滚动
                mOffsetY = 0f
            } else if (mOffsetY > overflowLength) { //滑动到底部，并且最后一个item即将脱离底部时
                //如果列表能滚动的话，则直接设置为可滑动的最大距离，避免最后一个item向上移
                if (itemLength > pathLength) {
                    mOffsetY = overflowLength.toFloat()
                } else {
                    //如果列表内容很少，不用滚动就能显示完的话，就不更新offset
                    //那为什么这里是减呢？因为最上面执行了一句+=，所以现在这样做是抵消第一句的操作。
                    mOffsetY -= offsetY
                }
            }
        }


    }


    /**
     * @return Item总长度
     */
    private fun getItemLength(): Int {
        //这里 +1 是为了让最后一个item 显示出来 (让最后一个item的距离相对于Path长度的百分比<1，
        // 即使其满足mKeyframes.getValue()方法里面获取有效坐标点的条件)
        return itemCount * mItemOffset - mItemOffset + 1
    }


    /**
     * 根据当前设置的滚动方向来获取对应的滚动偏移量
     */
    private fun getScrollOffset(): Float {
        return if (mOrientation == RecyclerView.VERTICAL) mOffsetY else mOffsetX
    }


    override fun canScrollVertically(): Boolean {
        //设置了滑动方向是垂直，才能接受垂直滚动事件
        return mOrientation == RecyclerView.VERTICAL
    }

    override fun canScrollHorizontally(): Boolean {
        //设置了滑动方向是水平，才能接受水平滚动事件
        return mOrientation == RecyclerView.HORIZONTAL
    }

}